export default function useMapUtils() {
    let map: any
    let eventOffFns: (() => void)[] = []
    const sourceIds = new Set<string>()
    const layerIds = new Set<string>()

    function register(m: any) {
        map = m
        return map
    }

    function setSource(id: string, data: any, options: Record<string, any> & { cover?: boolean }) {
        checkMap()
        const { cover, ...rest } = options || {}
        const jsonData = {
            type: 'FeatureCollection',
            features: data,
        }
        if (!map.getSource(id)) {
            map.addSource(id, {
                type: 'geojson',
                data: jsonData,
                ...rest,
            })
            sourceIds.add(id)
        } else {
            const source = map.getSource(id)

            jsonData.features = cover ? data : [...source._data.features, ...data]
            source.setData(jsonData)
        }
    }

    function setLayer(
        id: string,
        options: Record<string, any> & { before?: string },
        hasLayerCallback?: (layer: any) => void,
    ) {
        checkMap()
        const { before, ...rest } = options
        const layer = map.getLayer(id)
        if (!layer) {
            map.addLayer(
                {
                    // id: '1',
                    // source
                    id,
                    ...(rest as any),
                },
                before,
            )
            layerIds.add(id)
        } else {
            hasLayerCallback?.(layer)
        }
    }

    function loadImage(name: string, url: string, options?: { width?: number }) {
        checkMap()
        if (map.hasImage(name)) {
            return Promise.resolve()
        }
        return new Promise<void>((resolve, reject) => {
            const image = new Image()
            image.src = url
            image.onload = () => {
                const { width } = options || {}
                image.width = width || 32
                image.height = (image.width / image.naturalWidth) * image.naturalHeight

                if (!map.hasImage(name)) {
                    map.addImage(name, image)
                }
                resolve()
            }
            image.onerror = err => {
                reject(err)
            }
            // map.loadImage(url, (error, image) => {
            //   if (error) {
            //     resolve()
            //   }
            //   if (!map.hasImage(name)) {
            //     map.addImage(name, image)
            //   }
            //   resolve()
            // })
        })
    }

    function addEvent(
        ...args:
            | [type: string, layer: string | readonly string[], listener: (ev: any) => void]
            | [type: string, listener: (ev: any) => void]
    ) {
        checkMap()
        if (args.length === 2) {
            const [type, listener] = args
            eventOffFns.push(() => map.off(type, listener))
            return map.on(type, listener)
        }

        const [type, layer, listener] = args
        eventOffFns.push(() => map.off(type, layer, listener))
        return map.on(type, layer, listener)
    }

    function removeAllEvents() {
        eventOffFns.forEach(fn => fn())
        eventOffFns = []
    }

    function removeAllSoucesAndLayers() {
        layerIds.forEach(id => {
            if (map.getLayer(id)) {
                map.removeLayer(id)
            }
        })
        sourceIds.forEach(id => {
            if (map.getSource(id)) {
                map.removeSource(id)
            }
        })
        layerIds.clear()
        sourceIds.clear()
    }

    function zoomToCenterWhenShowPopup(
        coordinates: any,
        popupHeight = 450,
        params = { speed: 0.6, curve: 1.0 },
    ) {
        checkMap()
        if (map.getPitch() === 0) {
            // 二维模式
            const screenLocation = map.project(coordinates) // lnglat -> xy
            screenLocation.y = screenLocation.y - popupHeight / 2
            const newCoordinates = map.unproject(screenLocation) // xy -> lnglat
            map.easeTo({
                center: [newCoordinates.lng, newCoordinates.lat],
                ...params,
            })
        } else {
            const bounds = map.getBounds()

            const offsetY =
                (((bounds._ne.lat - bounds._sw.lat) / window.innerHeight) * popupHeight) / 2

            map.easeTo({
                center: [coordinates[0], coordinates[1] + offsetY],
                ...params,
            })
        }
    }

    function removeMap() {
        checkMap()
        map.remove()
    }

    function checkMap() {
        if (!map) {
            throw new Error('map is not registered')
        }
    }

    return {
        register,
        getMap() {
            checkMap()
            return map
        },
        setSource,
        setLayer,
        loadImage,
        addEvent,
        removeAllEvents,
        removeAllSoucesAndLayers,
        removeMap,
        zoomToCenterWhenShowPopup,
    }
}
